<?

/** Generic date and time functions.
 *
 *  This collection of functions (libary) is licensed under GPL2.
 *  See LICENSE or www.gnu.org for more details.
 *  @author Jyry Kuukkanen
 *  @copyright Jyry Kuukkanen
 */


include_once("sostring.php");
include_once("sogeneric.php");

/** Weekdays as names 
 *  OBSOLETE ?????? */
define("SO_MON", 1);
define("SO_TUE", 2);
define("SO_WED", 3);
define("SO_THU", 4);
define("SO_FRI", 5);
define("SO_SAT", 6);
define("SO_SUN", 7);


/** Months as names 
 *  OBSOLETE ?????? */
define("SO_JAN", 1);
define("SO_FEB", 2);
define("SO_MAR", 3);
define("SO_APR", 4);
define("SO_MAY", 5);
define("SO_JUN", 6);
define("SO_JUL", 7);
define("SO_AUG", 8);
define("SO_SEP", 9);
define("SO_OCT", 10);
define("SO_NOW", 11);
define("SO_DEC", 12);


/** Time measurements */
define("SO_SECOND", 0);
define("SO_MINUTE", 1);
define("SO_HOUR", 2);
define("SO_DAY", 3);
define("SO_MONTH", 4);
define("SO_YEAR", 5);

/** Day in seconds */
define(SECS_PER_DAY, 86400);


/** Converts a date string to Delphi style float.
 *  A string containing a date in a form of NxNxN where N is a numeric value and
 *  x any non-numeric character is converted to Unix time stamp according to
 *  specified style: from European, Japanese or brain damage USA format.
 *  @param string $InStr String to convert.
 *  @param char $Style Date style to convert from: "e"=European d/m/y,
 *      "j"=Japanese y/d/m, "u"=silly m/d/y style
 *  @return int Date as Unix timestamp or NULL for invalid date
 *  @package sodatetime
 */
function soStrToDate($InStr, $Style = "e") {
    /* Check that input string is string and not empty */
    if (!is_string($InStr) || !$InStr) return NULL; // <-- EXIT HERE

    switch ($Style) {
        case "u" :
            list($month, $day, $year) = split("[^0-9]", $InStr);
            break;
        case "j" :
            list($year, $month, $day) = split("[^0-9]", $InStr);
            break;
        default: /* European */
            list($day, $month, $year) = split("[^0-9]", $InStr);
            /* Very cunning code to fill in the current month + year when the
             * user is feeling lazy. */
            if (!$month) $month = date("n", time());
            if (!$year) $year = date("y", time());
    };

    if (checkdate($month, $day, $year)) {
        $result = floor(mktime(0, 0, 0, $month, $day, $year) /SECS_PER_DAY) +1;
    } else {
        $result = NULL;
    };

    return $result;
}; // soStrToDate


/** Converts a time string to Delphi style float.
 *  A string containing a time in a form of NxNxN where N is a numeric value and
 *  x any non-numeric character is converted to Delphi style stime stamp.
 *  @param string $InStr String to convert.
 *  @param bool $HourLimit When not set, more than 24 hours are accepted, otherwise
 *     time get rejected.
 *  @return int Time as Unix timestamp or NULL for invalid time
 *  @package sodatetime
 */
function soStrToTime($InStr, $HourLimit = 0) {
    list($hour, $minute, $second) = split("[^0-9]", $InStr);
    if (strtoupper(substr($InStr, strlen($InStr) -2, 2)) == "PM") {
        $hour += 12;
    };

    if (($hour >= 0) && (($HourLimit && ($hour < 24)) || (!$HourLimit)) &&
        ($minute >= 0) && ($minute < 60) &&
        ($second >= 0) && ($second < 60)) {
        $result = mktime($hour, $minute, $second, 1, 1, 1970);
        $result = ($hour *3600 +$minute *60 +$second) /SECS_PER_DAY;

        /* GMT fix */
# Un comment the following lines if PHP fucks up with times
#        $time_of_day = gettimeofday();
#        $mins_west = $time_of_day["minuteswest"];
#        $result = $result -($mins_west /1440);
    } else {
        $result = NULL;
    };

    return $result;
}; // soStrToTime


/** Converts a datetime string to Delphi style float timestamp.
 *  Date and time that are delimited by space are converted to float timestamp.
 *  @param string $Instr Datetime as space delimited string: "date time"
 *  @param string $Style Date style, see soStrToDate for details.
 *  @return int Datetime as Unix timestamp or NULL when string does not contain valid
 *      datetime string.
 *  @package sodatetime
 */
function soStrToDateTime($InStr, $Style = "e") {
    $split = soSplitStr($InStr, " ", 1, 0);
    if (count($split) == 0) {
        $result = NULL;
    } else {
        $date = soStrToDate($split[0], $Style);
        $time = soStrToTime($split[1], 1);

        /* Note! When time has >= 24 hours, it's considered invalid and
         * ignored, it otherword time becomes 0 */

        $result = $date +$time;
    };

    return $result;
}; // soStrToDateTime


/** Formats a time stamp according to specified style.
 *  Returns a formatted string of input float time stamp. A type is
 *  specified to declare whether to return a date, time or datetime string.
 *  Style is specified to declare in which format and what delimiter to use
 *  to return the time stamp in.
 *
 *  Style with "date" type: "sdy0"
 *  ------------------------------
 *  The first character "s" specifies the style: "e"=European d/m/y,
 *  "j"=Japanese y/m/d and "u"=Silly m/d/y -- defaults to "e". Second
 *  character "d" specifies the delimiter character to separate one element
 *  from another -- defaults to ".". Third character "y" specifies the
 *  number of digits to presents the year in (2 or 4) -- defaults to 2. The
 *  fourth character "0" is optional and when set to "0" (nough) pads monts
 *  and days with preceeding "0" when required so.
 *
 *  Style with "time" type: "hhdal0"
 *  --------------------------------
 *  The first two characters "hh" must be either "12" (12-hours followed by
 *  "am" or "pm") or "24" (normal 24-hours presentation) -- defaults to "24".
 *  The third character "d" specifies the delimiter character to separate one
 *  element from another -- defaults to ":". The fourth character "a" specifies
 *  the accuracy of the time: "s" or "-"=return hours, minutes and secord,
 *  "m"=return hours and minutes only "h"=return hours only. The fifth
 *  character "l" specifies the left limiter where to stop from right. Uh,
 *  how to put this. Well, this can be either "h" hours, "m" minutes or "s"
 *  secords and when set to, say "m", then all the hours are returned as
 *  minutes. Example "1:30:00" would return "90:00" (1h=60min). You can skip
 *  this specification with "-" (dash).
 *
 *  If anyone gets a better name for this than "left limiter", then thank you
 *  and let me know. The sixth character is optional and when set to "0"
 *  (nough) pads hours, minutes and seconds with preceeding "0" when required
 *  so.
 *
 *  Example: soFormatTime(time(), "date", "")
 *      might return "1.12.2000" (1st Dec 2000)
 *  Example: soFormatTime(time(), "time", "")
 *      might return "13:45:00")
 *  Example: soFormatTime(time(), "date", "e-40")
 *      might return "01-12-2000" (1st Dec 2000)
 *  Example: soFormatTime(time(), "time", "24:sm0")
 *      might return 825:00 (13h45min)
 *  Example: soFormatTime(time(), "datetime", "e-40 24:s--")
 *      might return "01-12-2000 13:45:00"
 *  @param int $TimeStamp Float time stamp to format.
 *  @param string $Type Either "time" or "date" or "datetime"
 *  @param string $Style This depends on type. With "datetime" type, both date and time
 *      are specified by delimiting their style string with a space.
 *  @package sodatetime
 */
function soFormatTime($TimeStamp, $Type, $Style = "") {
    if (ltrim(trim($TimeStamp)) == "") return ""; //  <-- EXIT!!!

    switch ($Type) {
        case "date":
            /* "sdy0"=style,delim,year,digits,0-padding */
            $delim = substr($Style, 1, 1);
            if ($delim == "") $delim = ".";

            if (substr($Style, 2, 1) == "4") {
                $year = "Y";
            } else {
                $year = "y";
            };

            if (strlen(substr($Style, 3, 1))) {
                $month = "m";
                $day = "d";
            } else {
                $month = "n";
                $day = "j";
            };

            switch (substr($Style, 0, 1)) {
                case "j": $fmt = "$year$delim$month$delim$day";
                          break;
                case "u": $fmt = "$month$delim$day$delim$year";
                          break;
                default:  $fmt = "$day$delim$month$delim$year";
            }; // switch $Style
            $result = date($fmt, floor($TimeStamp) *SECS_PER_DAY);
            break; // date

        case "time":
            $stamp = ($TimeStamp -floor($TimeStamp)) *24;
            $h = abs(floor($stamp));
            $stamp = ($stamp -$h) *60;
            $m = abs(floor($stamp));
            $stamp = ($stamp -$m) *60;
            $s = abs(floor($stamp));

            $ampm = (substr($Style, 0, 2) == "12");
            $padding = (substr($Style, 5, 1) == "0");

            $delim = substr($Style, 2, 1);
            if ($delim == "") $delim = ":";

            $accuracy = substr($Style, 3, 1);
            if ($accuracy == "") $accuracy = "-";

            $left_limit = substr($Style, 4, 1);

            if ($ampm) {
                if (($h > 11) && ($h < 24)) {
                    $h = $h -12;
                    $ampm_postfix = "am";
                } else {
                    $ampm_postfix = "pm";
                };
            }; // if $ampm

            if ($padding) {
                $h = soPadStr($h, "0", 2, 1);
                $m = soPadStr($m, "0", 2, 1);
                $s = soPadStr($s, "0", 2, 1);
            };

            $result = $h;
            if ($accuracy != "h") $result = soJoinStr($result, $delim, $m, 1);
            if (soStrPos("s-", $accuracy) != -1) {
                $result = soJoinStr($result, $delim, $s, 1);
            };
            if ($ampm) {
                $result = soJoinStr($result, $Delim, $ampm_postfix, 1);
            };
            break; // time

        default: /* datetime */
            $split = soSplitStr($Style, " ", 1, 0);
            if (count($split) != 0) {
                $result = soFormatTime($TimeStamp, "date", $split[0]);

                /* In order to get correct time from a timestamp,
                 * the date above sorted must be subtracted from the
                 * timestamp, don't ask me why --jq */
                $date = soStrToDate($result, substr($split[0], 0, 1));
                $result .= " ".soFormatTime($TimeStamp -$date, "time",
                                            $split[1]);
            } else {
                $result = "";
            };
    }; // switch $Type

    return $result;
}; // soFormatTime


/** Creates a timestamp
 *  Creates a time stamp for a given time. All parameters must not be given.
 *  @param int $Sec seconds
 *  @param int $Min minutes
 *  @param int $Hour Hours
 *  @param int $Day Day in the month
 *  @param int $Month Month
 *  @param int $Year Year
 *  @return int    UNIX timestamp
 * XXX Henkka, what's the purpose of this thingy? --jq
 *  @package sodatetime
 */
#function soMkTime($Sec, $Min=0, $Hour=0, $Day=0, $Month=0, $Year=0) {
#    if ($Day == 0) {
#        $Day = 1;
#    }
#    if ($Month == 0) {
#        $Month = 1;
#    }
#    if ($Year == 0) {
#        $Year = 1;
#    }
#
#    $dayStamp = mktime(0, 0, 0, $Month, $Day, $Year);
#    $timestamp = $dayStamp + (3600*$Hour+60*$Min+$Sec);
#
#    return $timestamp;
#} //soMkTime


/** Gets the specified information
 *  Extract the required information from the given time stamp. If no
 *      timestamp is given the current time is used.
 *  @param int $Type Type of information to extract. See definitions in the
 *      beginning of the file
 *  @param int $Time Timestamp to use
 *  @return int   Required information
 *  @package sodatetime
 */
function soGetTime($Type, $Time = NULL) {
    if (soisNull($Time)) $Time = time();
    $stamp = localtime($Time);
    $result = $stamp[$Type];

    switch ($Type) {
        case SO_MONTH:
            $result += 1;
            break;
        case SO_YEAR:
            $result += 1900;
            break;
    }; // switch

    return $result;
} // soGetTime


/** Converts dates/times between internal timestamp float number and string
 *  Converts items in an array from timestamp to string or vice versa.
 *  @param string $InArray Input data.
 *  @param SODT_* $TimeType Time type (date, time, datetime)
 *  @param bool $ToFloat When set(1), converts from string to float,
 *      otherwise converts from float to string.
 *  @param $InvalidAsIs When set(1), returns invalid input values as is,
 *      otherwise returns them as nough.
 *  @return bool Returns true(1) on success or false(0) on error.
 *  @package sodatetime
 */
function soConvertDates(&$InArray, $TimeType, $DateFormatStr,
                        $TimeFormatStr, $ToFloat = 1, $InvalidAsIs = 0) {
    switch ($TimeType) {
        case SODT_DATE:
            $format_str = SODT_DATE;
            break;
        case SODT_DATETIME:
            $format_str = SODT_DATE." ".SODT_TIME;
            break;
        case SODT_TIME:
            $format_str = " ".SODT_TIME;
            break;
        default: // all others
            return 0; // <-- EXIT
    }; // switch

    $format_str = str_replace(SODT_DATE, $DateFormatStr,
                              $format_str);
    $format_str = str_replace(SODT_TIME, $TimeFormatStr,
                              $format_str);

    $vcount = count($InArray);
    $space_pos = soStrPos($format_str, " ");
    for ($v = 0; $v < $vcount; $v++) {
        if ($ToFloat) {
            if (!soIsNull($InArray[$v]) && !soIsEmptyStr($InArray[$v])) {
                if ($TimeType == SODT_TIME) $InArray[$v] = " ".$InArray[$v];
                if ($TimeType == SODT_DATE) $InArray[$v] = $InArray[$v]." ";
                $result = soStrToDateTime($InArray[$v],
                                          substr($format_str, 0, 1));
                if (soIsNull($result) && $InvalidAsIs) $result = $InArray[$v];
                $InArray[$v] = $result;
            };
        } else {
            switch ($space_pos) {
                case -1 :
                    $type = "date";
                    break;
                case 0  :
                    $type = "time";
                    break;
                default:
                    $type = "datetime";
            }; // swicth
            if (!soIsNull($InArray[$v]) && !soIsEmptyStr($InArray[$v])) {
                $InArray[$v] = soFormatTime($InArray[$v], $type,
                                            trim($format_str));
            };
        };
    }; // for $v

    return 1;
}; // soConvertDates


/** Returns Unix timestamp as Delphi style float
 *  Delphi style time stamp consists of interger and decimal parts where
 *  integer represents days since 1.1.1970 and decimal fraction 1 per seconds
 *  since 0:00:00.
 *  @param int/NULL $TimeStamp Unix time stamp to convert, NULL=now
 *  @return dec Unix time stamp as Delphi style float
 *  @package sodatetime
 */
function soTime($TimeStamp = NULL) {
    if (soIsNull($TimeStamp)) $TimeStamp = time();
    return $TimeStamp /SECS_PER_DAY;
}; // soTime


/** Returns both current timestamp and microseconds as an array.
 *  Works like soTime with $TimeStamp == NULL, but returns an array of two
 *  elements: the 1st containing current timestamp and the 2nd with
 *  microseconds (float, 0 < x < 1).
 *  Example. souTime() could return array(17234.5687, 0.244);
 *  @return array/any Current timestamp and microsecs.
 *  @package sodatetime
 */
function souTime() {
    $utime = soExplode(microtime(), " ");
    return array($utime[1] /SECS_PER_DAY, $utime[0]);
}; // soTime

#$x = soStrToTime("12:12:12");
#echo $x."\n";
#echo soFormatTime($x, "time", "24:sm0");

?>
